﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using blackhouse.BlackApart.Business.Apartments;
using blackhouse.BlackApart.Business.Prices;
using blackhouse.BlackApart.Business.ApartmentServices;

namespace blackhouse.BlackApart.Business.Reservations {
    public class Variant {

        #region Fields

        private List<VariantItem> items = new List<VariantItem>();
        private VariantItem[] itemsPriv;
        private bool isSet = false;
        private bool itIsChangeInLocation;
        private bool areVariousApartmentTypes;
        private int apartmentCount;
        private IApartmentPrice price;
        private IApartmentPrice reservationPrice;

        #endregion

        #region Properties

        public VariantItem[] Items {
            get {
                if (this.itemsPriv == null)
                    this.SetInstance();
                return this.itemsPriv;
            }
        }

        /// <summary>
        /// This property determines whether there is a change in the stay premises
        /// </summary>
        public bool ItIsChangeInLocation {
            get {
                this.SetInstance();
                return this.itIsChangeInLocation;
            }
            private set { this.itIsChangeInLocation = value; }
        }

        /// <summary>
        /// This property determines whether the reservation will be broken up between different apartments
        /// </summary>
        public bool AreVariousApartmentTypes {
            get {
                this.SetInstance();
                return this.areVariousApartmentTypes;
            }
            private set { this.areVariousApartmentTypes = value; }
        }

        public int ApartmentCount {
            get {
                this.SetInstance();
                return this.apartmentCount;
            }
            private set { this.apartmentCount = value; }
        }

        public IApartmentPrice Price {
            get {
                this.SetInstance();
                return this.price;
            }
            private set { this.price = value; }
        }

        public IApartmentPrice ReservationPrice {
            get {
                this.SetInstance();
                return this.reservationPrice;
            }
            private set { this.reservationPrice = value; }
        }

        #endregion

        #region Methods

        private void SetInstance() {
            if (this.isSet) return;
            this.itemsPriv = this.items.ToArray();
            this.SetItIsChangeInLocation();
            this.AreVariousApartmentTypes = this.Items.Select(v => v.Apartment.Group.Id).Distinct().Count() > 1;
            this.ApartmentCount = this.Items.Length;
            DateTime arrivalDate = DateTime.MinValue, departureDate = DateTime.MinValue;
            if (this.Items.Length > 0) {
                arrivalDate = this.Items.Min(v => v.ArrivalDate);
                departureDate = this.Items.Max(v => v.DepartureDate);
            }

            if (this.Items.Length > 1) {
                ComputedApartmentPrice cap = new ComputedApartmentPrice(arrivalDate, departureDate);
                foreach (VariantItem vi in this.Items) {
                    cap.AddApartmentPrice(vi.Apartment, vi.NumberOfPeoples);
                }
                this.Price = cap;
                this.ReservationPrice = cap;
            }
            else if (this.Items.Length == 1) {
                IApartmentPrice price = Factory.Instance().GetApartmentPrice(this.Items[0].Apartment.Id);
                IApartmentPrice reservationPrice = price;
                price.ReservationFromDay = arrivalDate;
                price.ReservationToDay = departureDate;
                price.ReservationNumberOfPeoples = this.Items[0].NumberOfPeoples;
                if (this.Items[0].ExtraBedService != null) {
                    price.ReservationNumberOfPeoples = price.ReservationNumberOfPeoples - 1;
                    price = new PriceWithAdditionalServices(price, this.Items[0].ExtraBedService);
                }
                this.Price = price;
                this.ReservationPrice = reservationPrice;
            }
            this.isSet = true;
        }

        private void SetItIsChangeInLocation() {
            if (this.Items.Length == 0)
                this.ItIsChangeInLocation = false;
            else {
                DateTime minArrivalDate = this.Items.Min(v => v.ArrivalDate).Date;
                DateTime maxDepartureDate = this.Items.Min(v => v.DepartureDate).Date;
                this.ItIsChangeInLocation = this.Items.Any(v => v.ArrivalDate.Date > minArrivalDate || v.DepartureDate.Date < maxDepartureDate);
            }
        }

        private void Reset(bool resetItems) {
            this.itemsPriv = null;
            this.ItIsChangeInLocation = false;
            this.AreVariousApartmentTypes = false;
            this.ApartmentCount = 0;
            this.isSet = false;
            if (resetItems)
                this.items.Clear();
        }

        public VariantItem AddItem(IApartment apartment, DateTime arrivalDate, DateTime departureDate, int numberOfPeoples) {
            return this.AddItem(apartment, arrivalDate, departureDate, numberOfPeoples, null);
        }

        public VariantItem AddItem(IApartment apartment, DateTime arrivalDate, DateTime departureDate, int numberOfPeoples, IAdditionalService extraBedService) {
            this.Reset(false);
            VariantItem vi = new VariantItem();
            vi.Apartment = apartment;
            vi.ArrivalDate = arrivalDate;
            vi.DepartureDate = departureDate;
            vi.NumberOfPeoples = numberOfPeoples;
            vi.ExtraBedService = extraBedService;
            this.items.Add(vi);
            return vi;
        }

        public IApartmentGroup[] GetGroups() {
            return this.Items.Select(v => v.Apartment.Group).Distinct().ToArray();
        }

        #endregion

        #region Overrides For Identification

        public override int GetHashCode() {
            return this.Items.Sum(vi => vi.GetHashCode());
        }

        public override bool Equals(object other) {
            return other is Variant && this.Equals((Variant)other);
        }

        private bool Equals(Variant obj) {
            if ((obj as object) == null) return false;
            this.SetInstance();
            return Array.TrueForAll(obj.Items, vi => this.Items.Contains(vi));
        }

        public static bool operator ==(Variant a, Variant b) {
            if (System.Object.ReferenceEquals(a, b)) {
                return true;
            }

            if (((object)a == null) || ((object)b == null)) {
                return false;
            }

            return a.Equals(b);
        }

        public static bool operator !=(Variant lhs, Variant rhs) {

            return !(lhs == rhs);
        }

        #endregion

    }
}
